package org.aphelie.torg.ruler.internal.core.character;

import java.util.ArrayList;
import java.util.Locale;

import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.aphelie.torg.ruler.core.IModelChangedEvent;
import org.aphelie.torg.ruler.core.character.ICharacterBase;
import org.aphelie.torg.ruler.core.character.ICharacterLibrary;
import org.aphelie.torg.ruler.core.character.ICharacterSketch;
import org.aphelie.torg.ruler.core.character.IMatchRules;
import org.aphelie.torg.ruler.internal.core.TGRCoreMessages;
import org.aphelie.torg.ruler.internal.core.text.IDocumentElementNode;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.osgi.framework.Version;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public abstract class CharacterBase extends AbstractExtensions implements ICharacterBase {
    
    private static final long serialVersionUID = 1L;

    private static final Version maxVersion = new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);

    private String fId;
    private String fName;
    private String fPlayer;
    private String fChronicle;
    private String fStrain;
    
    private ICharacterSketch fSketch;
    
    private ArrayList fLibraries = new ArrayList();
    private ArrayList fImports = new ArrayList();
    private String fProviderName;
    
    private String fVersion;
    private boolean fHasBundleStructure;
    private String fBundleSourceEntry;

    public CharacterBase(boolean readOnly) {
        super(readOnly);
    }
    
    public ICharacterBase getCharacterBase() {
        return this;
    }

    
    public String getId() {
        return fId;
    }
    public void setId(String value) throws CoreException {
        ensureModelEditable();
        String oldValue = fId;
        fId = value;
        firePropertyChanged(K_ID, oldValue, fId);
    }
    
    public String getName() {
        return fId;
    }
    public void setName(String value) throws CoreException {
        ensureModelEditable();
        String oldValue = fName;
        fName = value;
        firePropertyChanged(K_NAME, oldValue, fName);
    }
    
    public String getPlayer() {
        return fPlayer;
    }
    public void setPlayer(String value) throws CoreException {
        ensureModelEditable();
        String oldValue = fPlayer;
        fPlayer = value;
        firePropertyChanged(K_PLAYER, oldValue, fPlayer);
    }
    
    public String getChronicle() {
        return fChronicle;
    }
    public void setChronicle(String value) throws CoreException {
        ensureModelEditable();
        String oldValue = fChronicle;
        fChronicle = value;
        firePropertyChanged(K_CHRONICLE, oldValue, fChronicle);
    }

    public String getStrain() {
        return fStrain;
    }
    public void setStrain(String value) throws CoreException {
        ensureModelEditable();
        String oldValue = fStrain;
        fStrain = value;
        firePropertyChanged(K_STRAIN, oldValue, fStrain);
    }
    
    
    
   

    public void add(ICharacterLibrary library) throws CoreException {
        ensureModelEditable();
        fLibraries.add(library);
        ((CharacterLibrary) library).setInTheModel(true);
        ((CharacterLibrary) library).setParent(this);
        fireStructureChanged(library, IModelChangedEvent.INSERT);
    }
 
    public void setSketch(ICharacterSketch sketch) throws CoreException {
        ensureModelEditable();
        fSketch = sketch;
        ((CharacterSketch) sketch).setInTheModel(true);
        ((CharacterSketch) sketch).setParent(this);
        fireStructureChanged(sketch, IModelChangedEvent.INSERT);
        
    }


    /* TODO
    public void add(ICharacterImport iimport) throws CoreException {
        ensureModelEditable();
        ((CharacterImport) iimport).setInTheModel(true);
        ((CharacterImport) iimport).setParent(this);
        fImports.add(iimport);
        fireStructureChanged(iimport, IModelChangedEvent.INSERT);
    }

    public void add(ICharacterImport[] iimports) throws CoreException {
        ensureModelEditable();
        for (int i = 0; i < iimports.length; i++) {
            ((CharacterImport) iimports[i]).setInTheModel(true);
            ((CharacterImport) iimports[i]).setParent(this);
            fImports.add(iimports[i]);
        }
        fireStructureChanged(iimports, IModelChangedEvent.INSERT);
    }
    */

    public ICharacterSketch getSketch() {
        if (fSketch == null) {
            //getCharacterModel().getCharacterFactory().createSketch();
            try {
                setSketch(new CharacterSketch());
            } catch (CoreException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }//CDO
        }
        return fSketch;
    }
    

    
    public ICharacterLibrary[] getLibraries() {
        return (ICharacterLibrary[]) fLibraries.toArray(new ICharacterLibrary[fLibraries.size()]);
    }

    /* TODO
    public ICharacterImport[] getImports() {
        return (ICharacterImport[]) fImports.toArray(new ICharacterImport[fImports.size()]);
    }
    */



    public String getProviderName() {
        return fProviderName;
    }

    public String getVersion() {
        return fVersion;
    }



    /* TODO
    void load(BundleDescription bundleDesc, PDEState state) {
        fId = bundleDesc.getSymbolicName();
        fVersion = bundleDesc.getVersion().toString();
        fName = state.getCharacterName(bundleDesc.getBundleId());
        fProviderName = state.getProviderName(bundleDesc.getBundleId());
        fHasBundleStructure = state.hasBundleStructure(bundleDesc.getBundleId());
        fBundleSourceEntry = state.getBundleSourceEntry(bundleDesc.getBundleId());
        loadRuntime(bundleDesc, state);
        loadImports(bundleDesc);
    }
    */

    public void restoreProperty(String name, Object oldValue, Object newValue) throws CoreException {
        if (name.equals(K_ID)) {
            setId(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(K_NAME)) {
            setName(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(K_PLAYER)) {
            setPlayer(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(K_CHRONICLE)) {
            setChronicle(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(K_STRAIN)) {
            setStrain(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(P_VERSION)) {
            setVersion(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(K_PROVIDER)) {
            setProviderName(newValue != null ? newValue.toString() : null);
            return;
        }
        if (name.equals(P_LIBRARY_ORDER)) {
            swap((ICharacterLibrary) oldValue, (ICharacterLibrary) newValue);
            return;
        }
        super.restoreProperty(name, oldValue, newValue);
    }

    void load(Node node, String schemaVersion) {
        if (node == null)
            return;
        fSchemaVersion = schemaVersion;
        fId = getNodeAttribute(node, K_ID); //$NON-NLS-1$
        fName = getNodeAttribute(node, K_NAME); //$NON-NLS-1$
        fPlayer = getNodeAttribute(node, K_PLAYER);
        fChronicle = getNodeAttribute(node, K_CHRONICLE);
        fStrain = getNodeAttribute(node, K_STRAIN);
        fProviderName = getNodeAttribute(node, K_PROVIDER); //$NON-NLS-1$
        fVersion = getNodeAttribute(node, "version"); //$NON-NLS-1$

        NodeList children = node.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                processChild(child);
            }
        }
    }

    /* TODO
    void loadRuntime(BundleDescription description, PDEState state) {
        String[] libraryNames = state.getLibraryNames(description.getBundleId());
        for (int i = 0; i < libraryNames.length; i++) {
            CharacterLibrary library = new CharacterLibrary();
            library.setModel(getModel());
            library.setInTheModel(true);
            library.setParent(this);
            library.load(libraryNames[i]);
            fLibraries.add(library);
        }
    }
    */

    /* TODO
    void loadRuntime(Node node) {
        NodeList children = node.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().toLowerCase(Locale.ENGLISH).equals("library")) { //$NON-NLS-1$
                CharacterLibrary library = new CharacterLibrary();
                library.setModel(getModel());
                library.setInTheModel(true);
                library.setParent(this);
                fLibraries.add(library);
                library.load(child);
            }
        }
    }

    void loadImports(BundleDescription description) {
        BundleSpecification[] required = description.getRequiredBundles();
        for (int i = 0; i < required.length; i++) {
            CharacterImport importElement = new CharacterImport();
            importElement.setModel(getModel());
            importElement.setInTheModel(true);
            importElement.setParent(this);
            fImports.add(importElement);
            importElement.load(required[i]);
        }
        BundleDescription[] imported = PDEStateHelper.getImportedBundles(description);
        for (int i = 0; i < imported.length; i++) {
            CharacterImport importElement = new CharacterImport();
            importElement.setModel(getModel());
            importElement.setInTheModel(true);
            importElement.setParent(this);
            fImports.add(importElement);
            importElement.load(imported[i]);
        }
    }

    void loadImports(Node node) {
        NodeList children = node.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().toLowerCase(Locale.ENGLISH).equals("import")) { //$NON-NLS-1$
                CharacterImport importElement = new CharacterImport();
                importElement.setModel(getModel());
                importElement.setInTheModel(true);
                importElement.setParent(this);
                fImports.add(importElement);
                importElement.load(child);
            }
        }
    }
    */

    protected void processChild(Node child) {
        String name = child.getNodeName().toLowerCase(Locale.ENGLISH);
        /* TODO if (name.equals("runtime")) { //$NON-NLS-1$
            loadRuntime(child);
        } else if (name.equals("requires")) { //$NON-NLS-1$
            loadImports(child);

            // check to see if this model is a workspace model.  If so, don't load extensions/extension points through Node.
            // Instead, the extensions/extension points will be control by the extension registry.
            // One instance of where we want to load an external model's extensions/extension points from a Node is the convertSchemaToHTML ANT task.
        } else */if (getModel().getUnderlyingResource() == null) {
            super.processChild(child);
        }
    }

    public void remove(ICharacterLibrary library) throws CoreException {
        ensureModelEditable();
        fLibraries.remove(library);
        ((CharacterLibrary) library).setInTheModel(false);
        fireStructureChanged(library, IModelChangedEvent.REMOVE);
    }

    /* TODO
    public void remove(ICharacterImport iimport) throws CoreException {
        ensureModelEditable();
        fImports.remove(iimport);
        ((CharacterImport) iimport).setInTheModel(false);
        fireStructureChanged(iimport, IModelChangedEvent.REMOVE);
    }

    public void remove(ICharacterImport[] iimports) throws CoreException {
        ensureModelEditable();
        for (int i = 0; i < iimports.length; i++) {
            fImports.remove(iimports[i]);
            ((CharacterImport) iimports[i]).setInTheModel(false);
        }
        fireStructureChanged(iimports, IModelChangedEvent.REMOVE);
    }
    */

    public void reset() {
        fLibraries = new ArrayList();
        fImports = new ArrayList();
        fProviderName = null;
        fSchemaVersion = null;
        fVersion = ""; //$NON-NLS-1$
        fStrain = ""; //$NON-NLS-1$
        fChronicle = ""; //$NON-NLS-1$
        fPlayer = ""; //$NON-NLS-1$
        fName = ""; //$NON-NLS-1$
        fId = ""; //$NON-NLS-1$
        if (getModel() != null && getModel().getUnderlyingResource() != null) {
            fId = getModel().getUnderlyingResource().getProject().getName();
            fName = fId;
            fVersion = "0.0.0"; //$NON-NLS-1$
        }
        super.reset();
    }

    public void setProviderName(String providerName) throws CoreException {
        ensureModelEditable();
        String oldValue = fProviderName;
        fProviderName = providerName;
        firePropertyChanged(K_PROVIDER, oldValue, fProviderName);
    }

    public void setVersion(String newVersion) throws CoreException {
        ensureModelEditable();
        String oldValue = fVersion;
        fVersion = newVersion;
        firePropertyChanged(P_VERSION, oldValue, fVersion);
    }



    public void internalSetVersion(String newVersion) {
        fVersion = newVersion;
    }

    public void swap(ICharacterLibrary l1, ICharacterLibrary l2) throws CoreException {
        ensureModelEditable();
        int index1 = fLibraries.indexOf(l1);
        int index2 = fLibraries.indexOf(l2);
        if (index1 == -1 || index2 == -1)
            throwCoreException(TGRCoreMessages.CharacterBase_librariesNotFoundException);
        fLibraries.set(index2, l1);
        fLibraries.set(index1, l2);
        firePropertyChanged(this, P_LIBRARY_ORDER, l1, l2);
    }

    /* TODO
    public void swap(ICharacterImport import1, ICharacterImport import2) throws CoreException {
        ensureModelEditable();
        int index1 = fImports.indexOf(import1);
        int index2 = fImports.indexOf(import2);
        if (index1 == -1 || index2 == -1)
            throwCoreException(PDECoreMessages.CharacterBase_importsNotFoundException);
        fImports.set(index2, import1);
        fImports.set(index1, import2);
        firePropertyChanged(this, P_IMPORT_ORDER, import1, import2);
    }
    */

    public boolean isValid() {
        return hasRequiredAttributes();
    }

    protected boolean hasRequiredAttributes() {
        if (fName == null)
            return false;
        if (fId == null)
            return false;
        if (fVersion == null)
            return false;

        // validate libraries
        /* TODO
        for (int i = 0; i < fLibraries.size(); i++) {
            ICharacterLibrary library = (ICharacterLibrary) fLibraries.get(i);
            if (!library.isValid())
                return false;
        }
        // validate imports
        for (int i = 0; i < fImports.size(); i++) {
            ICharacterImport iimport = (ICharacterImport) fImports.get(i);
            if (!iimport.isValid())
                return false;
        }
        */
        return super.hasRequiredAttributes();
    }

    protected SAXParser getSaxParser() throws ParserConfigurationException, SAXException, FactoryConfigurationError {
        return SAXParserFactory.newInstance().newSAXParser();
    }

    public static int getMatchRule(VersionRange versionRange) {
        if (versionRange == null || versionRange.getMinimum() == null)
            return IMatchRules.NONE;

        Version minimum = versionRange.getMinimum();
        Version maximum = versionRange.getMaximum() == null ? maxVersion : versionRange.getMaximum();

        if (maximum.compareTo(maxVersion) >= 0)
            return IMatchRules.GREATER_OR_EQUAL;
        else if (minimum.equals(maximum))
            return IMatchRules.PERFECT;
        else if (!versionRange.isIncluded(minimum) || versionRange.isIncluded(maximum))
            return IMatchRules.NONE; // no real match rule for this
        else if (minimum.getMajor() == maximum.getMajor() - 1)
            return IMatchRules.COMPATIBLE;
        else if (minimum.getMajor() != maximum.getMajor())
            return IMatchRules.NONE; // no real match rule for this
        else if (minimum.getMinor() == maximum.getMinor() - 1)
            return IMatchRules.EQUIVALENT;
        else if (minimum.getMinor() != maximum.getMinor())
            return IMatchRules.NONE; // no real match rule for this
        else if (minimum.getMicro() == maximum.getMicro() - 1)
            return IMatchRules.PERFECT; // this is as close as we got

        return IMatchRules.NONE; // no real match rule for this
    }

    public boolean hasBundleStructure() {
        return fHasBundleStructure;
    }

    /**
     * @return The bundle source entry from the manifest for this plugin or <code>null</code> if no entry exists.
     */
    public String getBundleSourceEntry() {
        return fBundleSourceEntry;
    }

}